package com.hs.wa.utls;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.springnew.entity.MySqlPrimary;
import org.apache.log4j.Logger;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.*;

import static java.lang.System.out;


/**
 * 重中之重 value 跟字段不能相等，的写个判断 不然查出来的数据不正确了，当运行查询的值是索引唯一的也行
 * redsi key 重复可以通过Mysql设计表解决
 *
 * @param <T>
 */
@Service
public class RedisCacheOfMysql<T> {
    private static final org.apache.log4j.Logger logger = Logger.getLogger(RedisCacheOfMysql.class);
    //https://cn.bing.com/search?q=google%E7%BF%BB%E8%AF%91
    private final String redisSplit = "_";
    private final String selectIndex = "@";
    private final String table = "*";
    private final String hashSplit = "|";
    private final String jsonSplit = "\"";
    private final String jsonOfSplit = ":";
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private JdbcTemplate jdbcTemplate;


    /**
     * 查询MySQL
     *
     * @param Class    Class
     * @param name     name
     * @param value    value
     * @param KeyClass KeyClass
     * @return T
     */
    public List<T> selectTableOfMysql(Class<T> Class, String name, String value, Class<T> KeyClass) {

        List Lists = selectRedis(Class, name, KeyClass, value);
        //Redis 查询出问题  查询数据库
        if (Lists.size() <= 0) {
            logger.info("redis 中查询不到 去数据库种获取" + new Date());
            //查询数据库
            Lists = selectOfMysql(Class, name, value);
//保存值到redis
            saveRedis(Class, name, value, KeyClass, Lists);
            return Lists;

        } else {
            logger.info("redis 查询到值 进行返回 " + new Date());
            logger.info("redis 查询到值: " + Lists);

            List<T> objects = JSONObject.parseArray(String.valueOf(Lists), Class);
            out.println(objects);

            return objects;
        }


    }

    /**
     * 存储 list 集合到redis
     * https://juejin.cn/post/6988488850712887309
     * https://juejin.cn/post/6988488850712887309
     * https://suozq.blog.csdn.net/article/details/86230914
     * <p>
     * 保存的时候以值进行保存，以保证唯一性， 不然查到的数据不符合
     *
     * @param aClass   aClass
     * @param keyClass keyClass
     * @param lists    lists
     * @param value    value
     */
    private void saveRedis(Class<T> aClass, String name, String value, Class<T> keyClass, List<T> lists) {
        {

            String redisKey = getRedisKey(aClass, keyClass, name, value);
            /*
             * https://blog.csdn.net/pengdandezhi/article/details/78909041
             * https://blog.csdn.net/antony9118/article/details/71023009
             * toString user 不能转为 String
             */

            //final String s = JSON.toJSONString(lists, (SerializeConfig) valList);
            for (T list : lists) {
                String jsonStr = JSON.toJSONString(list);
                logger.info("保存到redis中的 数据" + jsonStr);
                //redisTemplate.opsForList().leftPushAll(redisKey, jsonStr);
                redisTemplate.opsForHash().put(redisKey, value, jsonStr);
            }


        }

    }


    /**
     * 查询数据
     *
     * @param aClass aClass
     * @param name   name
     * @param value  value
     */
       private List<T> selectOfMysql(Class<T> aClass, String name, String value) {

//     *//*
//     * aClass.getName():com.hs.wa.entity.User
//     * aClass.getSimpleName():User
//     * 注意空格 跟and
//     * select * fromcom.hs.wa.entity.User where 1=1name='zhangsan'
//     *
//     * 不然是这样的
//     *//*
        String sql = "select * from " + aClass.getSimpleName() + " where  " + name + "='" + value + "'";
        logger.info("执行的sql ： " + sql);
        T t = null;
        try {
            t = aClass.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        assert t != null;
        Class<?> tClass = t.getClass();
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
        return MapToList(maps, tClass);
    }

    /**
     * 把jdbc 查询出来的数据进行转换
     *
     * @param maps        查询出的结果集合
     * @param entityClass 需要转换成为的类
     */
    private List<T> MapToList(List<Map<String, Object>> maps, Class<?> entityClass) {
        return null;
    }

    /**
     * 获取redis存储值
     *
     * @param tClass   tClass
     * @param name     name
     * @param keyClass keyClass
     * @param value    value
     * @return T
     */
    private List selectRedis(Class<T> tClass, String name, Class<T> keyClass, String value) {
        //此方法返回表示此类的所有声明字段的 Field 对象数组  https://vimsky.com/examples/usage/java-lang-class_getdeclaredfields-03.html
        //List<T> lists = new ArrayList<>();
        String index = getRedisKey(tClass, keyClass, name, value);
        out.println("redis key:*********  " + index);

        return redisTemplate.opsForHash().values(index);
    }

    /**
     * 获取redis 索引 可以用于存储和查询 redis
     *
     * @param aClass   aClass
     * @param keyClass keyClass
     * @param name     name
     * @param value    value
     * @return String
     */
    private String getRedisKey(Class<T> aClass, Class<T> keyClass, String name, String value) {
        String key = name + redisSplit + value;
        String simpleName = aClass.getSimpleName();
        Field[] declaredFields = keyClass.getDeclaredFields();
        StringBuilder redisIndex = new StringBuilder(simpleName + table);
        for (Field filed : declaredFields) {
            //不给接口加默认注解不能进来 焯  有默认注解都不能拼接了现在
            if (filed.isAnnotationPresent(MySqlPrimary.class)) {
                redisIndex.append(redisSplit).append(filed.getName());

            }
        }
        redisIndex.append(selectIndex).append(key);
        return redisIndex.toString();
    }


    /**
     * 查询MySQL
     *
     * @param
     * @param name  name
     * @param value value
     * @return T
     */
    public List<T> selectMysqlNoPrimaryKey(Class<T> Class, String name, String value) {

        List<T> Lists = (List<T>) selectRedisNoPrimaryKey(Class, name, value);
        //Redis 查询出问题  查询数据库
        if (Lists.size() <= 0) {
            logger.info("redis 中查询不到 去数据库种获取" + new Date());
            List<T> ts = selectOfMysql(Class, name, value);
//			System.out.println("map 转换 list 后  准备存储到redis 的数据" + Lists);
// if ("".equals(Lists) || "null".equals(Lists)) {
// 	Map<String, Object> stringObjectMap = jdbcTemplate.queryForMap(String.valueOf(Class));
// 	Lists.add((T) stringObjectMap);
// }
            //保存值到redis
            saveRedisNoPrimaryKey(Class, name, value, ts);
            return ts;

        } else {
            logger.info("redis 查询到值 进行返回 " + new Date());
            logger.info("redis 查询到值: " + Lists);

            List<T> objects = JSONObject.parseArray(String.valueOf(Lists), Class);
            out.println(objects);

            return objects;
        }


    }
    private void saveRedisNoPrimaryKey(Class<T> aClass, String name, String value, List<T> lists) {
        {

            String redisKey = getRedisKeyNoPrimaryKey(aClass, name, value);
            /**
             * https://blog.csdn.net/pengdandezhi/article/details/78909041
             * https://blog.csdn.net/antony9118/article/details/71023009
             * toString user 不能转为 String
             */

            //final String s = JSON.toJSONString(lists, (SerializeConfig) valList);
            for (T list : lists) {
                String jsonStr = JSON.toJSONString(list);
                logger.info("保存到redis中的 数据" + jsonStr);
                //redisTemplate.opsForList().leftPushAll(redisKey, jsonStr);
                redisTemplate.opsForHash().put(redisKey, value, jsonStr);
            }


        }

    }

    private List<?> selectRedisNoPrimaryKey(Class<T> tClass, String name, String value) {
        //此方法返回表示此类的所有声明字段的 Field 对象数组  https://vimsky.com/examples/usage/java-lang-class_getdeclaredfields-03.html
        //List<T> lists = new ArrayList<>();
        String index = getRedisKeyNoPrimaryKey(tClass, name, value);
        out.println("redis key:*********  " + index);
        List<? extends Object> lists = redisTemplate.opsForHash().values(index);

        return lists;
    }

    private String getRedisKeyNoPrimaryKey(Class<T> tClass, String name, String value) {
        String key = name + redisSplit + value;
        String redisIndex = tClass.getSimpleName();
        redisIndex += selectIndex + key;
        return redisIndex;
    }

    public List<T> selectMysqlForHashKey(Class<T> Class, String name, String value) {

        List list = selectRedisForHashKey(Class, name, value);
        //Redis 查询出问题  查询数据库
        if (list.size() <= 0) {
            logger.info("redis 中查询不到 去数据库种获取" + new Date());
            List<T> listMysql = selectOfMysql(Class, name, value);
            //保存值到redis
            saveRedisForHashKey(Class, name, value, listMysql);
            return listMysql;

        } else {
            logger.info("redis 查询到值: " + list);

            return JSONObject.parseArray(String.valueOf(list), Class);
        }


    }

    private void saveRedisForHashKey(Class<T> aClass, String name, String value, List<T> lists) {
        {

            String redisKey = getRedisKeyForHashKey(aClass);
            /*
             * https://blog.csdn.net/pengdandezhi/article/details/78909041
             * https://blog.csdn.net/antony9118/article/details/71023009
             * toString user 不能转为 String
             */

            //final String s = JSON.toJSONString(lists, (SerializeConfig) valList);
            String hashKey = getHashSplitKey(name, value);
            for (T list : lists) {
                String jsonStr = JSON.toJSONString(list);
                logger.info("保存到redis中的 数据" + jsonStr);
                //redisTemplate.opsForList().leftPushAll(redisKey, jsonStr);
                redisTemplate.opsForHash().put(redisKey, hashKey, jsonStr);
            }

        }

    }

    private List selectRedisForHashKey(Class<T> aClass, String name, String value) {
        //此方法返回表示此类的所有声明字段的 Field 对象数组  https://vimsky.com/examples/usage/java-lang-class_getdeclaredfields-03.html
        //List<T> lists = new ArrayList<>();
        String index = getRedisKeyForHashKey(aClass);
        out.println("redis key:*********  " + index);
        String hashKey = getHashSplitKey(name, value);
        Object o = redisTemplate.opsForHash().get(index, hashKey);
        List lists = Collections.singletonList(o);


        /*
         * 这样虽然可以 但是要是有人保保存了 hashKey 就完蛋了
         */
        out.println(lists);
        if (!"[null]".equals(lists.toString())) {
            out.println("存在 hashKey selectRedisForHashKey :  " + hashKey + lists);
            return lists;
        } else {
            out.println("不存在 hashKey selectRedisForHashKey :  " + hashKey + lists);
            return new ArrayList<>();
        }
    }

    /**
     * 为了组合json 键值对 判断是否需要差数据库
     * "username":"zhansan"
     *
     * @param name  name
     * @param value value
     * @return string
     */
    private String getHashSplitKey(String name, String value) {
        return jsonSplit + name + jsonSplit + jsonOfSplit + jsonSplit + value + jsonSplit;
    }

    private String getRedisKeyForHashKey(Class<T> aClass) {
        /*		String key = name + redisSplit + value;
		Field[] declaredFields = keyClass.getDeclaredFields();
		String redisIndex = simpleName + table;
		for (Field filed : declaredFields) {
			//不给接口加默认注解不能进来 焯  有默认注解都不能拼接了现在
			if (filed.isAnnotationPresent(MySqlPrimary.class)) {
				redisIndex += redisSplit + filed.getName();
*//*

			}
		}
		redisIndex += selectIndex + key;
		return redisIndex;*/
        return aClass.getSimpleName();
    }

    public List<T> selectMysqlForJdbcNoMap(Class<T> Class, String name, String value) {

        List list = selectRedisForHashKey(Class, name, value);
        //Redis 查询出问题  查询数据库
        if (list.size() > 0) {
            logger.info("redis 查询到值: " + list);

            return JSONObject.parseArray(String.valueOf(list), Class);
        } else {
            logger.info("redis 中查询不到 去数据库种获取" + new Date());
            //List<T> listMysql = selectOfMysql(Class, name, value);
            List<T> listTQuery = selectOfMySqlOfObject(Class, name, value);
//保存值到redis
            if (listTQuery.size() > 0) {
                saveRedisForHashKey(Class, name, value, listTQuery);
                return listTQuery;
            } else {
                logger.info("天啦噜 捏的数据被吃掉了哦");
                return null;
            }
        }
    }

    /**
     * name 定义表字段，value 定义字段数据 ：实现多字段查询
     * double flaot int 以后再考虑
     * @param Class
     * @param name
     * @param value
     * @return
     */
    public List<T> selectMysqlForJdbcNoMapOfStingArray(Class<T> Class, String[] name, String value) {

        List list = selectRedisForHashKeyStingArray(Class, name, value);
        //Redis 查询出问题  查询数据库
        if (list.size() > 0) {
            logger.info("redis 查询到值: " + list);

            return JSONObject.parseArray(String.valueOf(list), Class);
        } else {
            logger.info("redis 中查询不到 去数据库种获取" + new Date());
            //List<T> listMysql = selectOfMysql(Class, name, value);
            List<T> listTQuery = selectOfMySqlOfObjectStingArray(Class, name, value);

            String hashKey = getHashSplitKeyStingArray(name, value);
           //保存值到redis
            if (listTQuery.size() > 0) {
                saveRedisForHashKey(Class, hashKey, value, listTQuery);
                return listTQuery;
            } else {
                logger.info("天啦噜 捏的数据被吃掉了哦");
                return null;
            }
        }
    }


    /**
     * 数组分割查询数据库
     *
     * @param aClass
     * @param name
     * @param value
     * @return
     */
    private List<T> selectOfMySqlOfObjectStingArray(Class<T> aClass, String[] name, String value) {

        String[] v = value.split("\\|");
        StringBuffer wherePart = null;
        wherePart.append(" where ");
        for (int i = 0; i < name.length; i++) {
            wherePart.append(" and " + name[i] + " = " + "'" + v[i] + "' ");
        }


        String sql = "select * from " + aClass.getSimpleName() + wherePart;
        logger.info("数组分割查询数据库| 执行的sql ： " + sql);

        //https://www.jianshu.com/p/be60a81e2fe7

        return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(aClass));
    }

    /**
     * 传入name 为String[] 的表字段  | 分割的值 value 分割后数据与String[]长度要相等
     *
     * @param aClass
     * @param name
     * @param value
     * @return
     */
    private List selectRedisForHashKeyStingArray(Class<T> aClass, String[] name, String value) {
        //此方法返回表示此类的所有声明字段的 Field 对象数组  https://vimsky.com/examples/usage/java-lang-class_getdeclaredfields-03.html
        //List<T> lists = new ArrayList<>();
        String index = getRedisKeyForHashKeyStingArray(aClass, name, value);
        out.println("redis key:*********  " + index);
        String hashKey = getHashSplitKeyStingArray(name, value);
        Object o = redisTemplate.opsForHash().get(index, hashKey);
        List lists = Collections.singletonList(o);

        String[] v = value.split("\\|");

        /*
         * 这样虽然可以 但是要是有人保保存了 hashKey 就完蛋了
         */
        out.println(lists);
        if (!"[null]".equals(lists.toString())) {
            out.println("存在 hashKey selectRedisForHashKey :  " + hashKey + lists);
            return lists;
        } else {
            out.println("不存在 hashKey selectRedisForHashKey :  " + hashKey + lists);
            return new ArrayList<>();
        }
    }

    private String getRedisKeyForHashKeyStingArray(Class<T> aClass, String[] name, String value) {
        String str = null;
        for (int i = 0; i < name.length; i++) {
            str += name[i] + hashSplit;
        }

        return aClass.getSimpleName() + redisSplit + str + redisSplit + value;
    }

    private String getHashSplitKeyStingArray(String[] name, String value) {

        String str = null;
        for (int i = 0; i < name.length; i++) {
            str += name[i] + hashSplit;
        }

        return str + value;
    }

    /**
     * https://blog.csdn.net/weixin_33850015/article/details/93517860
     *
     * @param aClass aClass
     * @param name   name
     * @param value  value
     * @return T
     */
    private List<T> selectOfMySqlOfObject(Class<T> aClass, String name, String value) {

        /*
         * aClass.getName():com.hs.wa.entity.User
         * aClass.getSimpleName():User
         * 注意空格 跟and
         * select * fromcom.hs.wa.entity.User where 1=1name='zhangsan'
         *
         * 不然是这样的
         */
        String sql = "select * from " + aClass.getSimpleName() + " where  " + name + "='" + value + "'";
        logger.info("执行的sql ： " + sql);

        //https://www.jianshu.com/p/be60a81e2fe7

        return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(aClass));
    }
}